﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;

namespace GAscheduler
{
    class RulesParser
    {
        private RulesLexer m_rlLexer = new RulesLexer(); // lexer keeping track of a list of tokens
        private int m_iPos = 0;                          // position within the list of tokens
        private RuleToken m_CurrToken { get { return (RuleToken)m_rlLexer.m_alRuleTokens[m_iPos]; } }     // the token at the current position
        private RuleToken m_PrevToken { get { return (RuleToken)m_rlLexer.m_alRuleTokens[m_iPos - 1]; } } // the token one behind the current position

        /// <summary>
        /// Default constructor. Does nothing more than instantiate the class object.
        /// </summary>
        public RulesParser()
        { } // RulesParser

        /// <summary>
        /// Calls the lexer's populate function so that when the parser goes to 
        ///     parse, the lexer will have a list of tokens for it.
        /// </summary>
        /// <param name="sFilePath"></param>
        public void PreParse(string sFilePath)
        {
            m_rlLexer.PopulateLexer(sFilePath); // asks the lexer to populate itself based on a file path
        } // PreParse

        /// <summary>
        /// This is the real meat of this class. Parse looks for rule codes or the
        ///     end of the file, and depending on which it finds will call a function
        ///     designed to return a class object containing all the important data 
        ///     about that rule. These objects are then added to an ArrayList, which 
        ///     is returned once all rules have been parsed.
        /// </summary>
        /// <returns></returns>
        public ArrayList Parse()
        {
            ArrayList alDatabase = new ArrayList(); // database for all rule objects

            do // garenteed at least one pass
            {
                switch (m_CurrToken.GetTokenType()) // looking for specific token types
                {
                    case RuleToken.TokenType.RM:
                        {
                            alDatabase.Add(RM()); // call the RM function to get all the necessary data about that rule
                            break;                //    and add the resulting object to the database
                        }
                    case RuleToken.TokenType.CL:
                        {
                            alDatabase.Add(CL()); // call the CL function to get all the necessary data about that rule
                            break;                //    and add the resulting object to the database
                        }
                    case RuleToken.TokenType.BD:
                        {
                            alDatabase.Add(BD()); // call the BD function to get all the necessary data about that rule
                            break;                //    and add the resulting object to the database
                        }
                    case RuleToken.TokenType.DOT:
                        {
                            break; // if we have reached the end, no need to do anything more here (the while condition will end the loop)
                        }
                    default: // if we don't find what we're looking for, there's a problem
                        {
                            string strMsg = string.Format("Expecting rule code or end of file marker; found type {0} '{1}'",
                                m_CurrToken.GetTokenType(),
                                m_CurrToken.GetTokenName());

                            throw new Exception("RulesParser - BeginParse: " + strMsg); // so throw an exception
                        }
                }
            } while (m_CurrToken.GetTokenType() != RuleToken.TokenType.DOT);
            // as long as we haven't run out of rules to parse, keep going

            return alDatabase;
        } // Parse

        /// <summary>
        /// RM is responsible for creating a new RMCode class object with all the 
        ///     necessary data gleaned from the input file. Once it has done that,
        ///     the RMCode object is returned.
        /// </summary>
        /// <returns></returns>
        private RMCode RM()
        {
            RuleToken rtCode = m_PrevToken; // rule code
            bool bMust;                     // whether or not all classes in the list have to be held in the classroom
            RuleToken rtClassroom;          // the classroom
            ArrayList alClasses = new ArrayList(); // list of classes that can or must be held in the classroom

            Match(RuleToken.TokenType.COURSE);
            rtClassroom = m_PrevToken;  // assign the classroom

            // if all classes must be held in this room,
            if (m_CurrToken.GetTokenType() == RuleToken.TokenType.T)
            {
                bMust = true; // assign like so
            }
            else if (m_CurrToken.GetTokenType() == RuleToken.TokenType.F)
            {
                bMust = false; // if they can be held there, then assign like so
            }
            else // if we saw neither token, we have a problem
            {
                string strMsg = string.Format("Expected TokenType 'T' or TokenType 'F'; found {0} ('{1}')",
                    m_CurrToken.GetTokenType().ToString(),
                    m_CurrToken.GetTokenName());

                throw new Exception("RulesParser - Match: " + strMsg); // throw an exception
            }

            alClasses = GetArray(); // assign the full array of classes using the function GetArray

            // and return the new object with all the data
            return new RMCode(rtCode, rtClassroom.GetTokenName(), bMust, alClasses);
        } // RM

        /// <summary>
        /// CL is responsible for creating a new CLCode class object with all the 
        ///     necessary data gleaned from the input file. Once it has done that,
        ///     the CLCode object is returned.
        /// </summary>
        /// <returns></returns>
        private CLCode CL()
        {
            RuleToken rtCode = m_PrevToken; // rule code
            RuleToken rtCourse;             // course that may be held in the following rooms
            ArrayList alRooms;              // list of rooms the course may be held in

            Match(RuleToken.TokenType.COURSE);
            rtCourse = m_PrevToken;         // assign course

            alRooms = GetArray();           // assign the full array of classes using the function GetArray

            // and return the new object with all the data
            return new CLCode(rtCode, rtCourse.GetTokenName(), alRooms);
        } // CL

        /// <summary>
        /// BD is responsible for creating a new BDCode class object with all the 
        ///     necessary data gleaned from the input file. Once it has done that,
        ///     the BDCode object is returned.
        /// </summary>
        private BDCode BD()
        {
            RuleToken rtCode = m_PrevToken; // rule code
            RuleToken rtAttr = null;               // course attribute that can be held in the following buildings
            ArrayList alBuildings;          // list of buildings the attribute can be held in

            // as long as the token isn't a course or room, we will accept it as legitimate
            if (m_CurrToken.GetTokenType() != RuleToken.TokenType.COURSE)
                rtAttr = m_CurrToken;

            m_iPos++;                       // next token

            alBuildings = GetArray();       // assign the full array of classes using the function GetArray

            // and return the new object with all the data
            return new BDCode(rtCode, rtAttr.GetTokenName(), alBuildings);
        } // BD

        /// <summary>
        /// Gets the full list starting with the left bracket and ending with the right 
        ///     bracket. Lists can have multiple elements or just one element, but they 
        ///     may NOT be empty. Once all objects have been gathered, the list is 
        ///     returned.
        /// </summary>
        /// <returns></returns>
        private ArrayList GetArray()
        {
            ArrayList alTempList = new ArrayList(); // temporary list of elements

            Match(RuleToken.TokenType.LEFT_BRAC);   // identify beginning
            m_iPos--;                               // adjust position for the loop

            do // find objects to add to the list
            {
                m_iPos++;                            // next object
                Match(RuleToken.TokenType.COURSE);   // if it is of type course, we want it
                alTempList.Add(m_PrevToken);         // add it to the list
            } while (m_CurrToken.GetTokenType() != RuleToken.TokenType.RIGHT_BRAC); // keep going until we reach a right bracket

            Match(RuleToken.TokenType.RIGHT_BRAC); // then identify it

            return alTempList; // return the completed list
        } // GetArray

        /// <summary>
        /// Match is used to validate that the current token is expected. This code was originally 
        ///     written by Tom Fuller.
        ///     
        /// PRE:  The current token has been loaded.
        /// POST: The current token is verified and the next one loaded. If errors are encountered,
        ///       an exception is thrown.
        /// </summary>
        private void Match(RuleToken.TokenType tok)
        {
            // Have we loaded a token from the tokenizer?
            if (m_CurrToken == null)
            {
                throw new Exception("RulesParser - Match: m_CurrToken is null.");
            }

            // Is the current token the one we expected?
            if (m_CurrToken.GetTokenType() == tok)
            {
                // Is this the normal end of the source code file?
                if (tok == RuleToken.TokenType.DOT)
                {
                    return;                      // normal end of file
                } // if DOT

                // Otherwise load the next token
                m_iPos++; // get the next token
            }
            else
            { // We have the wrong token; bail out gracefully
                string strMsg = string.Format("Expected {0}; found {1} ('{2}')",
                    tok.ToString(), m_CurrToken.GetTokenType().ToString(),
                    m_CurrToken.GetTokenName());

                throw new Exception("RulesParser - Match: " + strMsg);
            }
        } // Match
    }
}
